import email
import smtplib
import imaplib
import os
import re
import base64
from email import message_from_bytes
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.application import MIMEApplication

from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout,
                             QLabel, QLineEdit, QTextEdit, QPushButton, QListWidget,
                             QFileDialog, QWidget, QMessageBox, QListWidgetItem)
from PyQt5.QtCore import Qt
from email.header import Header


class EmailClient(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("MIME邮件客户端 (QQ邮箱)")
        self.setGeometry(100, 100, 800, 600)

        # 初始化邮箱配置为空
        self.sender_email = ""
        self.auth_code = ""
        self.attachments = []
        self.inline_images = []  # 存储内嵌图片路径

        self.setup_ui()

    def _encode_header(self, text):
        """将中文等非ASCII内容编码为MIME合规格式"""
        return Header(text, 'utf-8').encode()

    def setup_ui(self):
        central_widget = QWidget()
        main_layout = QHBoxLayout()

        # 左侧：发送邮件面板
        send_layout = QVBoxLayout()

        # 添加邮箱配置输入框
        self.email_label = QLabel("QQ邮箱配置:")
        self.email_input = QLineEdit(placeholderText="输入QQ邮箱")
        self.auth_input = QLineEdit(placeholderText="输入授权码")
        self.auth_input.setEchoMode(QLineEdit.Password)  # 授权码显示为密码形式
        self.save_config_btn = QPushButton("保存配置")

        # 原发件人标签改为动态显示
        self.from_label = QLabel("发件人: 未配置")

        send_layout.addWidget(self.email_label)
        send_layout.addWidget(QLabel("QQ邮箱:"))
        send_layout.addWidget(self.email_input)
        send_layout.addWidget(QLabel("授权码:"))
        send_layout.addWidget(self.auth_input)
        send_layout.addWidget(self.save_config_btn)
        send_layout.addWidget(self.from_label)

        self.to_input = QLineEdit(placeholderText="收件人邮箱")
        self.subject_input = QLineEdit(placeholderText="邮件主题")
        self.body_input = QTextEdit(placeholderText="邮件正文（支持HTML）")

        self.attach_btn = QPushButton("添加附件")
        self.inline_img_btn = QPushButton("插入图片")
        self.clear_attach_btn = QPushButton("清空附件")
        self.send_btn = QPushButton("发送邮件")

        self.attach_list = QListWidget()

        send_layout.addWidget(QLabel("收件人:"))
        send_layout.addWidget(self.to_input)
        send_layout.addWidget(QLabel("主题:"))
        send_layout.addWidget(self.subject_input)
        send_layout.addWidget(QLabel("正文:"))
        send_layout.addWidget(self.body_input)
        send_layout.addWidget(QLabel("附件:"))
        send_layout.addWidget(self.attach_list)
        send_layout.addWidget(self.attach_btn)
        send_layout.addWidget(self.inline_img_btn)
        send_layout.addWidget(self.clear_attach_btn)
        send_layout.addWidget(self.send_btn)

        # 右侧：接收邮件面板
        receive_layout = QVBoxLayout()

        self.refresh_btn = QPushButton("刷新收件箱")
        self.email_list = QListWidget()
        self.email_content = QTextEdit()
        self.email_content.setReadOnly(True)

        receive_layout.addWidget(self.refresh_btn)
        receive_layout.addWidget(self.email_list)
        receive_layout.addWidget(self.email_content)

        main_layout.addLayout(send_layout, 40)
        main_layout.addLayout(receive_layout, 60)
        central_widget.setLayout(main_layout)
        self.setCentralWidget(central_widget)

        # 绑定事件
        self.save_config_btn.clicked.connect(self.save_email_config)
        self.attach_btn.clicked.connect(self.add_attachment)
        self.inline_img_btn.clicked.connect(self.add_inline_image)
        self.clear_attach_btn.clicked.connect(self.clear_attachments)
        self.send_btn.clicked.connect(self.send_email)
        self.refresh_btn.clicked.connect(self.load_emails)
        self.email_list.itemClicked.connect(self.show_email)

    def save_email_config(self):
        """保存邮箱配置"""
        email = self.email_input.text().strip()
        auth_code = self.auth_input.text().strip()

        if not email or not auth_code:
            QMessageBox.warning(self, "警告", "QQ邮箱和授权码不能为空！")
            return

        if not re.match(r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$', email):
            QMessageBox.warning(self, "警告", "请输入有效的邮箱地址！")
            return

        self.sender_email = email
        self.auth_code = auth_code
        self.from_label.setText(f"发件人: {email}")
        QMessageBox.information(self, "成功", "邮箱配置已保存！")

    def add_attachment(self):
        if not self._check_email_config():
            return
        files, _ = QFileDialog.getOpenFileNames(self, "选择附件", "", "All Files (*)")
        if files:
            for file in files:
                self.attachments.append(file)
                self.attach_list.addItem(QListWidgetItem(os.path.basename(file)))

    def add_inline_image(self):
        if not self._check_email_config():
            return
        file, _ = QFileDialog.getOpenFileName(self, "选择图片", "", "Images (*.png *.jpg *.jpeg *.gif *.bmp)")
        if file:
            # 生成唯一的Content-ID
            cid = f"image_{len(self.inline_images)}"
            self.inline_images.append((file, cid))

            # 在正文当前光标位置插入HTML图片标签
            cursor = self.body_input.textCursor()
            cursor.insertHtml(f'<img src="cid:{cid}" style="max-width: 300px;"/>')

            QMessageBox.information(self, "提示", "图片已插入到正文中，发送后将在邮件中显示")

    def clear_attachments(self):
        self.attachments.clear()
        self.inline_images.clear()
        self.attach_list.clear()

    def _check_email_config(self):
        """检查邮箱配置是否已设置"""
        if not self.sender_email or not self.auth_code:
            QMessageBox.warning(self, "警告", "请先设置QQ邮箱和授权码！")
            return False
        return True

    def _clean_qt_html(self, html):
        """清理Qt特有的HTML标签"""
        # 移除Qt特定的meta标签
        html = re.sub(r'<meta[^>]*>', '', html)
        # 移除Qt特定的style标签
        html = re.sub(r'<style[^>]*>.*?</style>', '', html, flags=re.DOTALL)
        # 简化body标签
        html = re.sub(r'<body[^>]*>', '<body>', html)
        return html
    def send_email(self):
        if not self._check_email_config():
            return

        recipient = self.to_input.text().strip()
        subject = self.subject_input.text().strip()
        body = self.body_input.toPlainText().strip()  # 使用 toPlainText() 获取原始内容

        if not recipient or not subject:
            QMessageBox.warning(self, "警告", "收件人和主题不能为空！")
            return

        try:
            msg = MIMEMultipart('related')
            msg['From'] = self.sender_email
            msg['To'] = recipient
            msg['Subject'] = self._encode_header(subject)
            msg['Date'] = email.utils.formatdate(localtime=True)

            # 获取正文内容
            body_text = self.body_input.toPlainText().strip()
            body_html = self.body_input.toHtml() if self.body_input.toPlainText().strip() else ""

            # 逻辑判断
            if not self.inline_images:
                # 情况1：没有内嵌图片，使用HTML检测逻辑
                if body_text and "<html>" in body_text.lower():
                    msg.attach(MIMEText(body_text, "html", "utf-8"))
                else:
                    msg.attach(MIMEText(body_text, "plain", "utf-8"))
            else:
                # 情况2：有内嵌图片，使用富文本模式
                if body_html:
                    # 清理Qt特有的HTML标签
                    clean_html = self._clean_qt_html(body_html)
                    msg.attach(MIMEText(clean_html, "html", "utf-8"))

            # 添加内嵌图片（保持不变）
            for img_path, cid in self.inline_images:
                with open(img_path, 'rb') as img_file:
                    img_data = img_file.read()
                img = MIMEImage(img_data)
                img.add_header('Content-ID', f'<{cid}>')
                img.add_header('Content-Disposition', 'inline',
                               filename=os.path.basename(img_path))
                msg.attach(img)

            # 添加附件（保持原逻辑）
            for file_path in self.attachments:
                with open(file_path, "rb") as f:
                    part = MIMEApplication(f.read(), Name=os.path.basename(file_path))
                    part.add_header('Content-Disposition', 'attachment',
                                    filename=self._encode_header(os.path.basename(file_path)))
                    msg.attach(part)

            # 发送邮件（保持原逻辑）
            with smtplib.SMTP_SSL("smtp.qq.com", 465) as server:
                server.login(self.sender_email, self.auth_code)
                server.sendmail(self.sender_email, recipient, msg.as_string())
                try:
                    server.quit()
                except:
                    server.close()

            QMessageBox.information(self, "完成", "邮件已提交到服务器！\n请检查QQ邮箱的发件箱确认")

            # 清空界面
            self.clear_attachments()
            self.to_input.clear()
            self.subject_input.clear()
            self.body_input.clear()

        except Exception as e:
            QMessageBox.critical(self, "错误",
                                 f"提交过程中断：{str(e)}\n"
                                 "请注意：邮件可能已进入发送队列，请检查QQ邮箱的发件箱")
    def _decode_header(self, header):
        """解码MIME编码的邮件头（如主题、发件人）"""
        if header is None:
            return "无"
        decoded = []
        for part, charset in email.header.decode_header(header):
            if isinstance(part, bytes):
                decoded.append(part.decode(charset or 'utf-8'))
            else:
                decoded.append(part)
        return "".join(decoded)

    def load_emails(self):
        try:
            self.email_list.clear()
            mail = imaplib.IMAP4_SSL("imap.qq.com", 993)
            mail.login(self.sender_email, self.auth_code)
            mail.select("INBOX")

            status, data = mail.search(None, "ALL")
            for eid in reversed(data[0].split()[-10:]):  # 最近10封
                status, data = mail.fetch(eid, "(RFC822)")
                msg = message_from_bytes(data[0][1])

                # 解码主题和发件人
                subject = self._decode_header(msg.get("Subject", "无主题"))
                from_ = self._decode_header(msg.get("From", "未知发件人"))
                date = msg.get("Date", "无日期")

                item = QListWidgetItem(f"{from_} | {subject} | {date}")
                item.setData(Qt.UserRole, msg)
                self.email_list.addItem(item)

            mail.close()
            mail.logout()
        except Exception as e:
            QMessageBox.critical(self, "错误", f"加载失败: {str(e)}")

    def show_email(self, item):
        """显示邮件内容并处理内嵌图片"""
        try:
            msg = item.data(Qt.UserRole)

            # 1. 解码邮件头部信息
            from_ = self._decode_header(msg['From'])
            to = self._decode_header(msg.get('To', '无收件人'))
            subject = self._decode_header(msg['Subject'])
            date = msg.get('Date', '无日期')

            # 2. 创建附件保存目录
            download_dir = os.path.join(os.path.expanduser("~"), "EmailAttachments")
            os.makedirs(download_dir, exist_ok=True)

            # 3. 初始化内容容器
            html_content = ""
            plain_content = ""
            attachments = []
            embedded_images = {}

            # 4. 解析MIME消息各部分
            for part in msg.walk():
                content_type = part.get_content_type()
                content_disp = str(part.get("Content-Disposition"))

                # 4.1 处理HTML正文
                if content_type == "text/html" and "attachment" not in content_disp:
                    charset = part.get_content_charset() or 'utf-8'
                    html_content = part.get_payload(decode=True).decode(charset, errors='replace')

                # 4.2 处理纯文本正文
                elif content_type == "text/plain" and "attachment" not in content_disp:
                    charset = part.get_content_charset() or 'utf-8'
                    plain_content = part.get_payload(decode=True).decode(charset, errors='replace')

                # 4.3 处理内嵌图片
                elif content_type.startswith("image/") or "inline" in content_disp:
                    cid = part.get("Content-ID", "").strip("<>")
                    if cid:
                        embedded_images[cid] = part.get_payload(decode=True)
                    elif part.get_filename():
                        # 如果没有CID但有文件名，也作为内嵌图片处理
                        filename = self._safe_filename(self._decode_header(part.get_filename()))
                        if filename:
                            embedded_images[filename] = part.get_payload(decode=True)

                # 4.4 处理附件保存
                elif part.get_filename() and "attachment" in content_disp:
                    filename = self._safe_filename(self._decode_header(part.get_filename()))
                    if filename:
                        filepath = self._get_unique_path(download_dir, filename)
                        try:
                            with open(filepath, "wb") as f:
                                f.write(part.get_payload(decode=True))
                            attachments.append({
                                "name": filename,
                                "path": filepath,
                                "size": os.path.getsize(filepath)
                            })
                        except Exception as e:
                            print(f"[附件保存失败] {filename}: {str(e)}")

            # 5. 构建最终显示内容
            content = f"""
            <div style="font-family: Arial; line-height: 1.6;">
                <div style="margin-bottom: 15px;">
                    <b>发件人:</b> {from_}<br>
                    <b>收件人:</b> {to}<br>
                    <b>日期:</b> {date}<br>
                    <b>主题:</b> {subject}
                </div>
                <hr style="border-top: 1px solid #ddd;">
            """

            # 5.1 处理邮件正文
            if html_content:
                # 替换内嵌图片CID为Base64
                for cid, img_data in embedded_images.items():
                    img_base64 = base64.b64encode(img_data).decode('ascii')
                    img_type = "image/jpeg"  # 默认类型
                    if isinstance(cid, str) and cid.lower().endswith(('.png', '.gif')):
                        img_type = f"image/{cid.split('.')[-1].lower()}"
                    html_content = html_content.replace(
                        f"cid:{cid}",
                        f"data:{img_type};base64,{img_base64}"
                    )
                    # 处理没有cid但直接引用的图片
                    html_content = html_content.replace(
                        f'src="{cid}"',
                        f'src="data:{img_type};base64,{img_base64}"'
                    )
                content += html_content
            elif plain_content:
                content += f'<pre style="white-space: pre-wrap;">{plain_content}</pre>'

            # 5.2 添加附件列表
            if attachments:
                content += """
                <hr style="border-top: 1px solid #ddd;">
                <div style="margin-top: 20px;">
                    <b>附件:</b>
                    <ul style="margin: 5px 0; padding-left: 20px; list-style-type: none;">
                """
                for att in attachments:
                    size_mb = att["size"] / (1024 * 1024)
                    content += f"""
                    <li style="margin-bottom: 8px;">
                        <span>{att['name']} ({size_mb:.2f} MB)</span>
                        <button onclick="window.open('file:///{att['path']}')" 
                                style="margin-left: 10px; 
                                       padding: 2px 8px;
                                       background: #4CAF50;
                                       color: white;
                                       border: none;
                                       border-radius: 3px;
                                       cursor: pointer;">
                            已保存到：C:/Users/29122/EmailAttachments
                        </button>
                    </li>
                    """
                content += "</ul></div>"

            content += "</div>"

            # 6. 渲染到界面
            self.email_content.setHtml(content)

        except Exception as e:
            error_msg = f"邮件解析失败: {str(e)}"
            self.email_content.setPlainText(error_msg)
            print(error_msg)

    def _safe_filename(self, filename):
        """确保文件名安全"""
        return re.sub(r'[\\/*?:"<>|]', "_", filename)

    def _get_unique_path(self, directory, filename):
        """生成唯一文件名"""
        base, ext = os.path.splitext(filename)
        counter = 1
        while os.path.exists(os.path.join(directory, filename)):
            filename = f"{base}({counter}){ext}"
            counter += 1
        return os.path.join(directory, filename)


if __name__ == "__main__":
    app = QApplication([])
    client = EmailClient()
    client.show()
    app.exec_()